
#include "wave/wave_io.h"
#include "logger.h"

#include <unistd.h> // for usleep

//#define MAX_READ_SAMPLES 160
#define MAX_READ_SAMPLES 3200

const std::string CWaveIO::sIniSectionName("WAVE_IO");

CWaveIO::CWaveIO
(
		EAction pAction,
		std::string pWavInputFile,
		std::string pWavOutputFile,
		double pDiscardTime
)
		:mError(INIT_FAILURE),
		mAction(pAction),
		mWavIn(NULL),
		mWavOut(new CRiffWaveWriter()),
		mReadByteBuffer(NULL),
		mReplayInput(false),
		mDiscardByteCount(0),
		mBytePerSample(0),
		mStreamInfo(),
		mReadInRealtime(false)
{
	if(!openInputFile(pWavInputFile)) {
		return;
	}

	// === mono output file ===
	if(!mWavOut->create(pWavOutputFile,
			mWavIn->getFormatTag(),
			mWavIn->channels(),
			mWavIn->frameRate(),
			mWavIn->bitsPerSample(),
			false)) {
		return;
	}

	mBytePerSample = mWavIn->bytePerSample();
	mStreamInfo.mSamplesWidth = getSampleWidth(mWavIn->bitsPerSample());
	mStreamInfo.mSampleRate = mWavIn->frameRate();
	mStreamInfo.mCodec = getCodec(mWavIn->getFormatTag());

	mDiscardByteCount = static_cast<unsigned int>((pDiscardTime) / (1.0 / mStreamInfo.mSampleRate) + 0.5);
	mDiscardByteCount *= mBytePerSample;

	if (mDiscardByteCount != 0) {
		printl(0, __FILE__, "Discarding %u bytes from output (%fs)", mDiscardByteCount, pDiscardTime);
	}

	mError = INIT_NO_ERROR;
}

CWaveIO::CWaveIO(EAction pAction, std::string pWavInputFile, bool pReplayInput)
		:mError(INIT_FAILURE),
		mAction(pAction),
		mWavIn(NULL),
		mWavOut(new CRiffWaveWriter()),
		mReadByteBuffer(NULL),
		mReplayInput(pReplayInput),
		mDiscardByteCount(0),
		mBytePerSample(0),
		mStreamInfo(),
		mReadInRealtime(false)
{
	if(!openInputFile(pWavInputFile)) {
		return;
	}
	mBytePerSample = mWavIn->bytePerSample();
	mStreamInfo.mSamplesWidth = getSampleWidth(mWavIn->bitsPerSample());
	mStreamInfo.mSampleRate = mWavIn->frameRate();
	mStreamInfo.mCodec = getCodec(mWavIn->getFormatTag());
	mError = INIT_NO_ERROR;
}

CWaveIO::CWaveIO
(
		EAction pAction,
		std::string pWavOutputFile,
		NAudioFormats::SStreamInfo pStreamInfo
)
		:mError(INIT_FAILURE),
		mAction(pAction),
		mWavIn(NULL),
		mWavOut(new CRiffWaveWriter()),
		mReadByteBuffer(NULL),
		mReplayInput(false),
		mDiscardByteCount(0),
		mBytePerSample(0),
		mStreamInfo(pStreamInfo),
		mReadInRealtime(false)
{
	unsigned int lBitsPerSample = getBitsPerSample(mStreamInfo.mSamplesWidth);
	if(lBitsPerSample != 8 && lBitsPerSample != 16) {
		printl(LL_ERR, __FILE__, "Doesn't support %u bits per sample.\n",
				lBitsPerSample);
		return;
	}

	if(mStreamInfo.mSampleRate == 0) {
		printl(LL_ERR, __FILE__, "Sample rate can't be 0.\n");
		return;
	}

	NWaveFormatTag::EFormatTag lFormatTag = getWaveFormatTag(mStreamInfo.mCodec);
	if(lFormatTag == NWaveFormatTag::UNKNOWN_FORMAT_TAG) {
		printl(LL_ERR, __FILE__, "Unknown wave format tag.\n");
		return;
	}

	int lChannels(0);
	switch(mAction) {
		case EMBED:
		case EXTRACT:
			lChannels = 1;
			break;
		case INTERLEAVED_EMBED_EXTRACT:
			lChannels = 2;
			break;
		case UNKNOWN_ACTION:
		case NO_ACTION:
		default:
			return;
	}
	// === mono output file ===
	if(!mWavOut->create(pWavOutputFile,
			lFormatTag, // PCM, ALAW or ULAW
			lChannels, // channels
			mStreamInfo.mSampleRate, // framerate
			lBitsPerSample,
			false)) {
		return;
	}

	mBytePerSample = (lBitsPerSample + 7) / 8;
	mError = INIT_NO_ERROR;
}

CWaveIO::~CWaveIO()
{
	if(mReadByteBuffer) {
		delete [] mReadByteBuffer;
	}
	if(mWavOut) {
		delete mWavOut;
	}
	if(mWavIn) {
		delete mWavIn;
	}
}

int CWaveIO::init(CPropertiesReader *pConfig)
{
	if(mError != INIT_NO_ERROR) {
		return -1;
	}
	std::string lStrValue;
	if(!pConfig->containsKey(sIniSectionName, "READ_SPEED_REALTIME")
	|| !pConfig->getValue(sIniSectionName, "READ_SPEED_REALTIME", lStrValue)) {
		printl(LL_ERR, __FILE__, "Config error: READ_SPEED_REALTIME [%s]\n", sIniSectionName.c_str());
		return -1;
	}
	if (lStrValue == "true" || lStrValue == "True") {
		mReadInRealtime = true;
	}
	return 0;
}

int CWaveIO::destroy()
{
	mError = INIT_FAILURE;
	return 0;
}

int CWaveIO::read(void** pData, EAction& pAction, int pTimeout)
{
	return read(pData, pAction, pTimeout, MAX_READ_SAMPLES);
}

int CWaveIO::read(void** pData, EAction& pAction, int pTimeout, int pMaxRead)
{
	if(!mWavIn || !mReadByteBuffer) {
		return -1;
	}
	if(mError != INIT_NO_ERROR) {
		return -1;
	}
	if(!pData) {
		return -1;
	}
	int lMaxSamples(pMaxRead / mBytePerSample);
	if (lMaxSamples <= 0) {
		return -1;
	}
	pAction = mAction;
	*pData = mReadByteBuffer;
	unsigned int lOffset(0);
	int lCount = mWavIn->getNextSamples(mReadByteBuffer, lMaxSamples, lOffset);
	if (mReplayInput && lCount != lMaxSamples) {
		mWavIn->begin();
		lCount = mWavIn->getNextSamples(mReadByteBuffer, lMaxSamples, lOffset);
		printl(0, __FILE__, "Restarting wave input, count: %d, offset: %u", lCount, lOffset);
	}
	
	if (mReadInRealtime) {
		if (mAction == EXTRACT) {
			//printl(0, __FILE__, "waiting at reading");
			usleep((static_cast<double>(lCount) / static_cast<double>(mStreamInfo.mSampleRate)) * 1000000.0);
		}
	}

	return lCount * mBytePerSample;
}

int CWaveIO::write(void* pData, const EAction& pAction, int pLength)
{
	if(!mWavOut) {
		return -1;
	}
	if(mError != INIT_NO_ERROR) {
		return -1;
	}
	if(!pData) {
		return -1;
	}
	if(pAction != mAction) {
		printl(LL_ERR, __FILE__, "Wrong action.\n");
		return -1;
	}

	unsigned int lLength(pLength);
	if (mDiscardByteCount > 0) {
		mDiscardByteCount -= pLength;

		if (mDiscardByteCount >= 0) {
			return lLength;
		}

		// mDiscardSampleCount has a negative value --> addition
		pData = static_cast<unsigned char*>(pData) + (lLength + mDiscardByteCount);
		pLength = -mDiscardByteCount; // is a subtraction
	}

	int rv(mWavOut->writeSamples(pData, pLength / mBytePerSample) * mBytePerSample);
	return (rv != pLength) ? rv : lLength;
}

NAudioFormats::SStreamInfo CWaveIO::getStreamInfo()
{
	return mStreamInfo;
}

bool CWaveIO::openInputFile(std::string pMonoWavInputFile)
{
	mWavIn = new CRiffWave();

	if(!mWavIn->open(pMonoWavInputFile.c_str())) {
		printl(LL_ERR, __FILE__, "Can't open wave file %s.\n", pMonoWavInputFile.c_str());
		return false;
	}

	switch(mAction) {
		case UNKNOWN_ACTION:
			printl(LL_ERR, __FILE__, "Unknown action.\n");
			return false;
		case NO_ACTION:
			printl(LL_ERR, __FILE__, "No action.\n");
			return false;
		case EMBED:
		case EXTRACT:
			if(mWavIn->channels() != 1) {
				printl(LL_ERR, __FILE__, "Wave file must be mono.\n");
				return false;
			}
			break;
		case INTERLEAVED_EMBED_EXTRACT:
			if(mWavIn->channels() != 2) {
				printl(LL_ERR, __FILE__, "Wave file must be stereo.\n");
				return false;
			}
			break;
		default:
			break;
	}

	mReadByteBuffer = new unsigned char[MAX_READ_SAMPLES * mWavIn->bytePerSample()];

	if(!mWavIn->begin()) {
		printl(LL_ERR, __FILE__, "Set wave file to begin failed.\n");
		return false;
	}
	return true;
}

NAudioFormats::ESamplesWidth CWaveIO::getSampleWidth(unsigned int pBitsPerSample)
{
	switch(pBitsPerSample) {
		case 8:
			return NAudioFormats::U8; // a wave file use for 8 bit unsigned
		case 16:
			return NAudioFormats::S16LE; // a wave file use little endian signed
	}
	printl(LL_ERR, __FILE__, "Sample width %u is unsupported.\n", pBitsPerSample);
	return NAudioFormats::UNKNOWN_WIDTH;
}

unsigned int CWaveIO::getBitsPerSample(NAudioFormats::ESamplesWidth pSampleWidth)
{
	switch(pSampleWidth) {
		case NAudioFormats::UNKNOWN_WIDTH:
			printl(LL_WARN, __FILE__, "Unknown sample width.\n");
			return 0;
		case NAudioFormats::S8:
			printl(LL_WARN, __FILE__, "8 bit sample should be unsigned.\n");
			return 8;
		case NAudioFormats::U8:
			return 8;
		case NAudioFormats::S16LE:
			return 16;
		case NAudioFormats::S16BE:
		case NAudioFormats::S16HO:
		case NAudioFormats::U16LE:
		case NAudioFormats::U16BE:
		case NAudioFormats::U16HO:
			printl(LL_WARN, __FILE__, "16 bit sample should be little endian and signed.\n");
			return 16;
		case NAudioFormats::DOUBLE:
			printl(LL_WARN, __FILE__, "Sample width for double is used.\n");
			return sizeof(double);
	}
	printl(LL_ERR, __FILE__, "Unsupported sample width.\n");
	return 0;
}

NAudioFormats::ECodec CWaveIO::getCodec(NWaveFormatTag::EFormatTag pWaveFormatTag)
{
	switch(pWaveFormatTag) {
		case NWaveFormatTag::PCM:
			return NAudioFormats::PCM;
		case NWaveFormatTag::ALAW:
			return NAudioFormats::ALAW;
		case NWaveFormatTag::MULAW:
			return NAudioFormats::ULAW;
		default:
			break;
	}
	printl(LL_ERR, __FILE__, "Unsupported codec.\n");
	return NAudioFormats::UNKNOWN_CODEC;
}

NWaveFormatTag::EFormatTag CWaveIO::getWaveFormatTag(NAudioFormats::ECodec pCodec)
{
	switch(pCodec) {
		case NAudioFormats::PCM:
			return NWaveFormatTag::PCM;
		case NAudioFormats::ALAW:
			return NWaveFormatTag::ALAW;
		case NAudioFormats::ULAW:
			return NWaveFormatTag::MULAW;
		default:
			break;
	}
	printl(LL_ERR, __FILE__, "Unsupported wave format tag.\n");
	return NWaveFormatTag::UNKNOWN_FORMAT_TAG;
}
